using System;
using System.Collections.Generic;
using System.Text;
using Mud.Common.Physical;
using Mud.Common.Messages;

using Mud.Common;

namespace Mud.Server.Physical
{
    /// <summary>
    /// This is the main filter between the creature and the creature controler. It pre-filters
    /// the events that come from the environment in order to match creatures logical
    /// state (you can not recieve an event that a creature said something if you can not see
    /// that creature, instead you receive an event that someone said something on creature location).
    /// </summary>
    public class Brain : IPhysicalEventReceiver
    {

        #region Properties

        /// <summary>
        /// This is the final controler which does the actual processing.
        /// </summary>
        public CreatureControler CreatureControler
        {
            get { return _controler; }
            set { _controler = value; }
        }
        private CreatureControler _controler;
        
        /// <summary>
        /// Creature that this brain is attached to.
        /// </summary>
        public Creature ControledCreature
        {
            get { return _controledCreature; }
        }
        private Creature _controledCreature;


        /// <summary>
        /// List of all creatures which are visible (detectable) to the creature.
        /// </summary>
        public IList<Creature> VisibleCreatures
        {
            get { return _visibleCreatures; }
        }
        private List<Creature> _visibleCreatures = new List<Creature>();

        /// <summary>
        /// List of all objects which are visible (detectable) to the creature.
        /// </summary>
        public IList<SimpleObject> VisibleObjects
        {
            get { return _visibleObjects; }
        }
        private List<SimpleObject> _visibleObjects = new List<SimpleObject>();


        #endregion

        #region IPhysicalEventReceiver Members


        public virtual void Receive(EventCreatureAppeared e)
        {
            if(e.Source != this.ControledCreature)
                this._visibleCreatures.Add(e.Source);
            _controler.Receive(e);
        }

        public virtual void Receive(EventCreatureDisappeared e)
        {
            if (e.Source != this.ControledCreature)
                this._visibleCreatures.Remove(e.Source);
            _controler.Receive(e);
        }

        public virtual void Receive(EventCreatureMoveStarted e)
        {
            if (!_visibleCreatures.Contains(e.Source))
                return;
            else
                _controler.Receive(e);
        }

        public virtual void Receive(EventCreatureMoveEnded e)
        {
            if (e.Source == this.ControledCreature)
            {
                _controler.Receive(e);
                CheckVisibleObjects(); // we have moved, check all visible objects
            }
            else
            {
                CheckCreatureVisibility(e.Source);
                if (_visibleCreatures.Contains(e.Source))
                    _controler.Receive(e);
            }
        }

        public virtual void Receive(EventCreatureSay e)
        {
            if (e.Source == this.ControledCreature) // no need to hear yourself
                return;

            if (!this.ControledCreature.CanHear(e.Source.Location, e.CommunicationType).Success)
                return;

            if (_visibleCreatures.Contains(e.Source))
            {
                _controler.Receive(e);
            }
            else
            {
                EventLocationSay newEvent = new EventLocationSay(e.CommunicationType, e.Message, e.Source.Location);
                newEvent.Send(this);
            }
        }

        public virtual void Receive(EventCreatureEnteredArea e)
        {
            if (e.Source == this.ControledCreature)
            {
                foreach (Creature c in _visibleCreatures)
                {
                    EventCreatureDisappeared newEvent = new EventCreatureDisappeared(c);
                    newEvent.Send(this);
                }
                foreach (SimpleObject o in _visibleObjects)
                {
                    EventSimpleObjectDisappeared newEvent = new EventSimpleObjectDisappeared(o);
                    newEvent.Send(this);
                }
                _visibleCreatures.Clear();
                _visibleObjects.Clear();
                _visibleCreatures.Add(this.ControledCreature); // you can always see yourself
                _controler.Receive(e);
                CheckVisibleObjects();
            }
            else
            {
                CheckCreatureVisibility(e.Source);
            }
        }

        public virtual void Receive(EventCreatureExitedArea e)
        {
            if (e.Source != this.ControledCreature)
                CheckCreatureVisibility(e.Source);
         }

        public void Receive(EventCreatureTurned e)
        {
            if (_visibleCreatures.Contains(e.Source))
                _controler.Receive(e);
        }




        public virtual void Receive(EventSimpleObjectAppeared e)
        {
            this._visibleObjects.Add(e.Source);
            _controler.Receive(e);
        }

        public virtual void Receive(EventSimpleObjectDisappeared e)
        {
            this._visibleObjects.Remove(e.Source);
            _controler.Receive(e);
        }



        public void Receive(EventLocationSay e)
        {
            if (this.ControledCreature.CanHear(e.Location, e.CommunicationType).Success)
                _controler.Receive(e);
        }

        #endregion

        #region Helper methods

        /// <summary>
        /// This method should be called each time when there is some event which might lead
        /// to the creature becoming in/visible to the player.
        /// </summary>
        /// <param name="c">Creature to check.</param>
        private void CheckCreatureVisibility(Creature c)
        {
            if (c == this.ControledCreature)
                return;

            if (this.ControledCreature.CanView(c).Success && !_visibleCreatures.Contains(c))
            {
                EventCreatureAppeared e = new EventCreatureAppeared(c);
                e.Send(this);
            }

            if (!this.ControledCreature.CanView(c).Success && _visibleCreatures.Contains(c))
            {
                EventCreatureDisappeared e = new EventCreatureDisappeared(c);
                e.Send(this);
            }

        }

        /// <summary>
        /// This method should be called each time when there is some event which might lead
        /// to the object becoming in/visible to the player.
        /// </summary>
        /// <param name="o">Object to check.</param>
        private void CheckSimpleObjectVisibility(SimpleObject o)
        {
            if (this.ControledCreature.CanView(o).Success && !_visibleObjects.Contains(o))
            {
                EventSimpleObjectAppeared e = new EventSimpleObjectAppeared(o);
                e.Send(this);
            }

            if (!this.ControledCreature.CanView(o).Success && _visibleObjects.Contains(o))
            {
                EventSimpleObjectDisappeared e = new EventSimpleObjectDisappeared(o);
                e.Send(this);
            }

        }

        /// <summary>
        /// Rechecks the vibility of all objects in the area when we move.
        /// </summary>
        private void CheckVisibleObjects()
        {
            foreach (SimpleObject o in ControledCreature.CurrentArea.SimpleObjects.Values)
            {
                CheckSimpleObjectVisibility(o);
            }
            foreach (Creature c in ControledCreature.CurrentArea.Creatures.Values)
            {
                CheckCreatureVisibility(c);
            }
        }

        #endregion

        public Brain(Creature creature, CreatureControler finalControler)
        {
            if (creature == null)
                throw new ArgumentNullException("creature");
            if (finalControler == null)
                throw new ArgumentNullException("finalControler");
            _controledCreature = creature;
            _controler = finalControler;
        }

    }
}
